//
//  MWXmppUnit.m
//  MWXmppUnit
//
//  Created by liu nian on 3/17/16.
//  Copyright © 2016 http://iliunian.cn. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "MWXmppUnit.h"
#import "XMPPFramework.h"
#import "XMPPReconnect.h"
#import "XMPPAutoPing.h"
#import <arpa/inet.h>
#import <netinet/in.h>
#import <CommonCrypto/CommonDigest.h>
#import "XMPPLogging.h"

static NSInteger kPingInterval = 5;

@interface MWXmppUnit ()<XMPPStreamDelegate, XMPPReconnectDelegate, XMPPAutoPingDelegate>
@property (nonatomic, retain) XMPPStream *xmppStream;
@property (nonatomic, retain) XMPPReconnect *xmppReconnect;
@property (nonatomic, strong) XMPPAutoPing *xmppAutoPing;

@property (nonatomic, assign, getter = isXmppConnectOK) BOOL xmppConnectOK;
@property (nonatomic, strong) dispatch_queue_t xmpp_queue_t;
@property (nonatomic, strong) NSTimer *runTimer;
//configure
@property (nonatomic, assign) NSInteger xmppHostPort;
@property (nonatomic, strong) NSString *xmppHostName;
@property (nonatomic, retain) NSString *xmppUserName;
@property (nonatomic, retain) NSString *xmppPassword;
@end

@implementation MWXmppUnit

#pragma mark public method
- (void)xmppConnect{
    //开启定时连接
    [self detachNewThreadMonitor];
    //连接XMPP服务器
    [self operateForXmpp];
}

- (void)xmppDisconnect{
    [self stopMonitor];
    self.xmppConnectOK = NO;
    [self xmppGetOffline];
    [self xmppTeardownStream];
}

#pragma mark private method
- (void)detachNewThreadMonitor{
    [self stopMonitor];
    [NSThread detachNewThreadSelector:@selector(networkThreadMonitor)
                             toTarget:self
                           withObject:nil];
}

- (void)stopMonitor{
    [_runTimer invalidate];
    _runTimer = nil;
}

- (void)networkThreadMonitor{
    @autoreleasepool {
        ////获得当前thread的Runloop
        if (![self.runTimer isValid]) {
            return ;
        }
        [self.runTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:0]];
    }
}

- (void)operateForXmpp{
    if (!self.isXmppConnectOK && [self networkReachabilityStatusReachable]){
        [self.xmppReconnect manualStart];
        if ([self xmppUserName] && [self xmppPassword]) {
            [self xmppConnect:[self xmppUserName] password:self.xmppPassword];
        }
    }
}

- (void)xmppTeardownStream{
    [_xmppStream removeDelegate:self delegateQueue:self.xmpp_queue_t];
    [_xmppStream disconnect];
    _xmppStream = nil;
    
    [_xmppReconnect deactivate];
    [_xmppReconnect removeDelegate:self delegateQueue:self.xmpp_queue_t];
    _xmppReconnect = nil;
    
    [_xmppAutoPing deactivate];
    [_xmppAutoPing removeDelegate:self delegateQueue:self.xmpp_queue_t];
    _xmppAutoPing = nil;
}

- (BOOL)xmppConnect:(NSString *)JID password:(NSString *)Password{
    if (JID == nil ||Password == nil){
        return NO;
    }
    if (self.xmppStream.isConnecting) {
        return NO;
    }
    
    if (self.xmppStream.isAuthenticating) {
        return NO;
    }
    
    if([self.xmppStream isConnected]){
        [self xmppDisconnect];
    }
    
    [self.xmppStream setMyJID:[XMPPJID jidWithString:JID]];
    
    NSError *error = nil;
    BOOL result = [self.xmppStream connectWithTimeout:60 error:&error];
    return result;
}

- (BOOL)xmppRegister{
    NSError *error = nil;
    BOOL result = [self.xmppStream registerWithPassword:self.xmppPassword error:&error];
    return result;
}

- (BOOL)xmppAuthenticate{
    if ([self.xmppStream isAuthenticated] || [self.xmppStream isAuthenticating]) {
        return NO;
    }
    
    NSError *error = nil;
    BOOL result = [self.xmppStream authenticateWithPassword:self.xmppPassword error:&error];
    return result;
}

- (void)xmppGetOnline{
    XMPPPresence*presence = [XMPPPresence presenceWithType:@"available"];
    [self.xmppStream sendElement:presence];
    [self.xmppAutoPing activate:self.xmppStream];
}

- (void)xmppGetOffline{
    XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
    [self.xmppStream sendElement:presence];
    [self.xmppAutoPing deactivate];
}

#pragma mark XMPPReconnectDelegate

#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_5
- (void)xmppReconnect:(XMPPReconnect *)sender didDetectAccidentalDisconnect:(SCNetworkConnectionFlags)connectionFlags{
}

- (BOOL)xmppReconnect:(XMPPReconnect *)sender shouldAttemptAutoReconnect:(SCNetworkConnectionFlags)connectionFlags{
    return YES;
}
#else
- (void)xmppReconnect:(XMPPReconnect *)sender didDetectAccidentalDisconnect:(SCNetworkReachabilityFlags)reachabilityFlags{
}

- (BOOL)xmppReconnect:(XMPPReconnect *)sender shouldAttemptAutoReconnect:(SCNetworkReachabilityFlags)reachabilityFlags{
    return YES;
}
#endif

#pragma mark XMPPAutoPingDelegate
- (void)xmppAutoPingDidSendPing:(XMPPAutoPing *)sender{
}

- (void)xmppAutoPingDidReceivePong:(XMPPAutoPing *)sender{
}

- (void)xmppAutoPingDidTimeout:(XMPPAutoPing *)sender{
}

#pragma mark XMPPStreamDelegate
- (void)xmppStreamWillConnect:(XMPPStream *)sender{
}

- (void)xmppStream:(XMPPStream *)sender socketDidConnect:(GCDAsyncSocket *)socket{
}

- (void)xmppStreamDidConnect:(XMPPStream *)sender{
    [self xmppAuthenticate];
}

- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
    if (![sender.myPresence.type isEqualToString:@"available"]) {
        [self xmppGetOnline];
    }
}

- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error{
    static BOOL tryRegister = YES;
    if (tryRegister){
        [self xmppRegister];
        tryRegister = NO;
    }
}

- (void)xmppStreamDidRegister:(XMPPStream *)sender{
    [self xmppAuthenticate];
}

- (void)xmppStream:(XMPPStream *)sender didNotRegister:(NSXMLElement *)error{
    [self.xmppStream disconnect];
}

- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq{
    if (iq.isGetIQ){
        NSXMLElement *query = iq.childElement;
        if ([query.name isEqualToString:@"ping"]){
            XMPPIQ *pong = [XMPPIQ iqWithType:@"result" to:[iq from] elementID:[iq elementID]];
            [sender sendElement:pong];
        }
    }
    return YES;
}
- (void)xmppStream:(XMPPStream *)sender didReceiveError:(id)error{
    
}
- (void)xmppStreamDidSecure:(XMPPStream *)sender{
    
}
- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings{
    NSString *expectedCertName = nil;
    NSString *serverDomain = sender.hostName;
    NSString *virtualDomain = [sender.myJID domain];
    
    if ([serverDomain isEqualToString:@"talk.google.com"]){
        if ([virtualDomain isEqualToString:@"gmail.com"]){
            expectedCertName = virtualDomain;
        }else{
            expectedCertName = serverDomain;
        }
    }else if (serverDomain == nil){
        expectedCertName = virtualDomain;
    }else{
        expectedCertName = serverDomain;
    }
    if (expectedCertName){
        [settings setObject:expectedCertName forKey:(NSString *)kCFStreamSSLPeerName];
    }
}

- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error{
    if (error) {
        [self xmppDisconnect];
        if ([self.configureDelegate respondsToSelector:@selector(xmppUnit:didDisconnectWithError:)]) {
            [self.configureDelegate xmppUnit:self didDisconnectWithError:error];
        }
    }
}

- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence{
    if ([presence.type isEqualToString:@"available"]) {
        self.xmppConnectOK = YES;
    }
}

- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
    NSString *body = [[message elementForName:@"body"] stringValue];
    @synchronized(self){
        dispatch_async(dispatch_get_main_queue(), ^{
            if ([self.configureDelegate respondsToSelector:@selector(xmppUnit:didReceiveBodyMessage:)]) {
                [self.configureDelegate xmppUnit:self didReceiveBodyMessage:body];
            }
        });
    }
}

#pragma mark getter protocol
- (NSString *)xmppHostName{
    if ([self.configureDelegate respondsToSelector:@selector(xmppHostName)]) {
        return [self.configureDelegate xmppHostName];
    }
    return nil;
}

- (NSInteger)xmppHostPort{
    if ([self.configureDelegate respondsToSelector:@selector(xmppHostPort)]) {
        return [self.configureDelegate xmppHostPort];
    }
    return 0;
}

- (NSString *)xmppUserName{
    if ([self.configureDelegate respondsToSelector:@selector(xmppUserName)]) {
        return [self.configureDelegate xmppUserName];
    }
    return nil;
}
- (NSString *)xmppPassword{
    if ([self.configureDelegate respondsToSelector:@selector(xmppPassword)]) {
        return [self.configureDelegate xmppPassword];
    }
    return nil;
}
- (BOOL)networkReachabilityStatusReachable{
    if ([self.configureDelegate respondsToSelector:@selector(networkReachabilityStatusReachable)]) {
        return [self.configureDelegate networkReachabilityStatusReachable];
    }
    return NO;
}

#pragma mark getter perporty
- (NSTimer *)runTimer{
    if (!_runTimer) {
        _runTimer = [NSTimer scheduledTimerWithTimeInterval:30
                                                     target:self
                                                   selector:@selector(operateForXmpp)
                                                   userInfo:nil
                                                    repeats:YES];
    }
    return _runTimer;
}

- (dispatch_queue_t)xmpp_queue_t{
    if (_xmpp_queue_t == nil) {
        _xmpp_queue_t = dispatch_queue_create("com.PusceneSerialQueue.xmpp", DISPATCH_QUEUE_SERIAL);
    }
    return _xmpp_queue_t;
}
- (XMPPStream *)xmppStream{
    if (!_xmppStream) {
        _xmppStream = [[XMPPStream alloc] init];
        _xmppStream.hostName = [self xmppHostName];
        _xmppStream.hostPort = [self xmppHostPort];
        [_xmppStream addDelegate:self delegateQueue:self.xmpp_queue_t];
    }
    return _xmppStream;
}

- (XMPPReconnect *)xmppReconnect{
    if (!_xmppReconnect) {
        _xmppReconnect = [[XMPPReconnect alloc] init];
        _xmppReconnect.autoReconnect = YES;
        [_xmppReconnect activate:self.xmppStream];
        [_xmppReconnect addDelegate:self delegateQueue:self.xmpp_queue_t];
    }
    return _xmppReconnect;
}

- (XMPPAutoPing *)xmppAutoPing{
    if (!_xmppAutoPing) {
        _xmppAutoPing = [[XMPPAutoPing alloc] initWithDispatchQueue:self.xmpp_queue_t];
        _xmppAutoPing.respondsToQueries = YES;
        _xmppAutoPing.pingInterval = kPingInterval;//ping 间隔时间
        [_xmppAutoPing addDelegate:self delegateQueue:self.xmpp_queue_t];
    }
    return _xmppAutoPing;
}

@end
